$console = jDev.module('jDev.console', {

  log: function(){
    $console._logFormatted(arguments, '');
  },
  
  debug: function(){
    $console._logFormatted(arguments, 'debug');
  },
  
  info: function(){
    $console._logFormatted(arguments, 'info');
  },
  
  warn: function(){
    $console._logFormatted(arguments, 'warning');
  },
  
  error: function(){
    $console._logFormatted(arguments, 'error');
  },
  
  assert: function(truth, message){
    if (!truth) {
      var a = Array.prototype.slice.call(arguments, 1);
      
      $console._logFormatted(a.length ? a : ['Assertion Failure'], 'error');
      throw message ? message : 'Assertion Failure';
    }
  },
  
  dir: function(object){
    var html = [];
                
    var pairs = [];
    for (var name in object){
      try{
        pairs.push([name, object[name]]);
      }catch (exc){}
    }
    
    pairs.sort(function(a, b) { return a[0] < b[0] ? -1 : 1; });
    
    html.push('<table>');
    for (var i = 0; i < pairs.length; ++i){
      var name = pairs[i][0], value = pairs[i][1];
      
      html.push('<tr>', 
          '<td class="propertyNameCell"><span class="propertyName">',
          $console._escapeHTML(name), 
          '</span></td><td><span class="propertyValue">'
        );

      $console._appendObject(value, html);
      html.push('</span></td></tr>');
    }
    html.push('</table>');
    
    $console._logRow(html, 'dir');
  },
  
  dirxml: function(node){
    var html = [];
    
    $console._appendNode(node, html);
    $console._logRow(html, 'dirxml');
  },
  
  group: function(){
    $console._logRow(arguments, 'group', pushGroup);
  },
  
  groupEnd: function(){
    $console._logRow(arguments, '', popGroup);
  },
  
  time: function(name){
    $console._timeMap[name] = (new Date()).getTime();
  },
  
  timeEnd: function(name){
    if (name in $console._timeMap){
      var delta = (new Date()).getTime() - $console._timeMap[name];
      $console._logFormatted([name+ ':', delta+'ms']);
      delete $console._timeMap[name];
    }
  },
  
  count: function(){
    $console.warn(['count() not supported.']);
  },
  
  trace: function(){
    $console.warn(['trace() not supported.']);
  },
  
  profile: function(){
    $console.warn(['profile() not supported.']);
  },
  
  profileEnd: function(){
    $console.warn(['profile() not supported.']);
  },
  
  clear: function(){
    $console._consoleBody.innerHTML = '';
  },
  
  open: function(){
    $console._toggleConsole(true);
  },
  
  close: function(){
    if ($console._frameVisible)
      $console._toggleConsole();
  }  
  
});

// ********************************************************************************************
   
$console.extend({
  _consoleFrame: null,
  _consoleBody: null,
  _commandLine: null,
  
  _frameVisible: false,
  _messageQueue: [],
  _groupStack: [],
  _timeMap: {},
  
  _clPrefix: '>>> ',
  
  _isFirefox: navigator.userAgent.indexOf('Firefox') != -1,
  _isIE: navigator.userAgent.indexOf('MSIE') != -1,
  _isOpera: navigator.userAgent.indexOf('Opera') != -1,
  _isSafari: navigator.userAgent.indexOf('AppleWebKit') != -1
});

// ********************************************************************************************
$console.extend({
  _toggleConsole: function(forceOpen){
    $console._frameVisible = forceOpen || !$console._frameVisible;
    if ($console._consoleFrame)
        $console._consoleFrame.style.visibility = $console._frameVisible ? 'visible' : 'hidden';
    else
        $console._waitForBody();
  },
  
  _focusCommandLine: function(){
    toggleConsole(true);
    if ($console._commandLine)
        $console._commandLine.focus();
  },
  
  _waitForBody: function(){
    if (document.body)
        $console._createFrame();
    else
        setTimeout($console._waitForBody, 200);
  }, 
  
  _createFrame: function(){
    if ($console._consoleFrame)
        return;
    
    window.onFirebugReady = function(doc){
        window.onFirebugReady = null;
  
        var toolbar = doc.getElementById('topSplitter');
        toolbar.onmousedown = $console._onSplitterMouseDown;
  
        $console._commandLine = doc.getElementById('commandLine');
        $console._addEvent($console._commandLine, 'keydown', $console._onCommandLineKeyDown);
  
        $console._addEvent(doc, $console._isIE || $console._isSafari ? 'keydown' : 'keypress', $console._onKeyDown);
        
        $console._consoleBody = doc.getElementById('log');
        $console._layout();
        $console._flush();
    },
  
    $console._baseURL = $console._getFirebugURL();
  
    $console._consoleFrame = document.createElement('iframe');
    $console._consoleFrame.setAttribute('src', $console._baseURL+'/firebug.html');
    $console._consoleFrame.setAttribute('frameBorder', '0');
    $console._consoleFrame.style.visibility = ($console._frameVisible ? 'visible' : 'hidden');    
    $console._consoleFrame.style.zIndex = '2147483583';
    $console._consoleFrame.style.position = document.all ? 'absolute' : 'fixed';
    $console._consoleFrame.style.width = '100%';
    $console._consoleFrame.style.left = '0';
    $console._consoleFrame.style.bottom = '0';
    $console._consoleFrame.style.height = '200px';
    document.body.appendChild($console._consoleFrame);
  },
  
  _getFirebugURL: function(){
    var scripts = document.getElementsByTagName('script');
    for (var i = 0; i < scripts.length; ++i){
      if (scripts[i].src.indexOf('firebug.js') != -1)
      {
        var lastSlash = scripts[i].src.lastIndexOf('/');
        return scripts[i].src.substr(0, lastSlash);
      }
    }
  },
  
  _evalCommandLine: function(){
    var text = $console._commandLine.value;
    $console._commandLine.value = '';
  
    $console._logRow([$console._clPrefix, text], 'command');
    
    var value;
    try {
        value = eval(text);
    } catch (exc){}
  
    $console.log(value);
  },
  
  _layout: function(){
    var toolbar = $console._consoleBody.ownerDocument.getElementById('topSplitter');
    var height = $console._consoleFrame.offsetHeight - (toolbar.offsetHeight + $console._commandLine.offsetHeight);
    height = Math.max(height, 0);
    $console._consoleBody.style.top = toolbar.offsetHeight + 'px';
    $console._consoleBody.style.height = height + 'px';
    
    $console._commandLine.style.top = ($console._consoleFrame.offsetHeight - $console._commandLine.offsetHeight) + 'px';
  }
});

$console.extend({
  _logRow: function(message, className, handler){
    if ($console._consoleBody)
      $console._writeMessage(message, className, handler);
    else
    {
      $console._messageQueue.push([message, className, handler]);
      $console._waitForBody();
    }
  },
  
  _flush: function(){
    var queue = $console._messageQueue;
    $console._messageQueue = [];
    
    for (var i = 0; i < queue.length; ++i)
      $console._writeMessage(queue[i][0], queue[i][1], queue[i][2]);
  },
  
  _writeMessage: function(message, className, handler){
    var isScrolledToBottom =
      $console._consoleBody.scrollTop + $console._consoleBody.offsetHeight >= $console._consoleBody.scrollHeight;
  
    if (!handler)
      handler = $console._writeRow;
    
    handler(message, className);
    
    if (isScrolledToBottom)
      $console._consoleBody.scrollTop = $console._consoleBody.scrollHeight - $console._consoleBody.offsetHeight;
  },
  
  _appendRow: function(row){
    var container = $console._groupStack.length ? $console._groupStack[$console._groupStack.length-1] : $console._consoleBody;
    container.appendChild(row);
  },
  
  _writeRow: function(message, className){
    var row = $console._consoleBody.ownerDocument.createElement('div');
    row.className = 'logRow' + (className ? ' logRow-'+className : '');
    row.innerHTML = message.join('');
    $console._appendRow(row);
  },
  
  _pushGroup: function(message, className){
    logFormatted(message, className);
  
    var groupRow = $console._consoleBody.ownerDocument.createElement('div');
    groupRow.className = 'logGroup';
    var groupRowBox = $console._consoleBody.ownerDocument.createElement('div');
    groupRowBox.className = 'logGroupBox';
    groupRow.appendChild(groupRowBox);
    $console._appendRow(groupRowBox);
    $console._groupStack.push(groupRowBox);
  },
  
  _popGroup: function(){
    $console._groupStack.pop();
  }
});


// ********************************************************************************************

$console.extend({
  _logFormatted: function(objects, className){
    var html = [];
  
    var format = objects[0];
    var objIndex = 0;
  
    if (typeof(format) != 'string')
    {
        format = '';
        objIndex = -1;
    }
  
    var parts = $console._parseFormat(format);
    for (var i = 0; i < parts.length; ++i){
    
      var part = parts[i];
      
      if (part && typeof(part) == 'object'){
        var object = objects[++objIndex];
        part.appender(object, html);
      }
      else
        $console._appendText(part, html);
    }
  
    for (var i = objIndex+1; i < objects.length; ++i) {
      $console._appendText(' ', html);
      
      var object = objects[i];
      if (typeof(object) == 'string')
        $console._appendText(object, html);
      else
        $console._appendObject(object, html);
    }
    
    $console._logRow(html, className);
  },
  
  _parseFormat: function(format){
    var parts = [];
  
    var reg = /((^%|[^\\]%)(\d+)?(\.)([a-zA-Z]))|((^%|[^\\]%)([a-zA-Z]))/;    
    var appenderMap = {s: $console._appendText, d: $console._appendInteger, i: $console._appendInteger, f: $console._appendFloat};
  
    for (var m = reg.exec(format); m; m = reg.exec(format)){
      var type = m[8] ? m[8] : m[5];
      var appender = type in appenderMap ? appenderMap[type] : $console._appendObject;
      var precision = m[3] ? parseInt(m[3]) : (m[4] == '.' ? -1 : 0);
  
      parts.push(format.substr(0, m[0][0] == '%' ? m.index : m.index+1));
      parts.push({appender: appender, precision: precision});
  
      format = format.substr(m.index+m[0].length);
    }
  
    parts.push(format);
  
    return parts;
  },
  
  _escapeHTML: function(value){
    function replaceChars(ch){
      switch (ch){
        case '<':
            return '&lt;';
        case '>':
            return '&gt;';
        case '&':
            return '&amp;';
        case "'":
            return '&#39;';
        case '"':
            return '&quot;';
      }
      return '?';
    };
    return String(value).replace(/[<>&"']/g, replaceChars);
  },
  
  _objectToString: function(object){
    try {
      return object+'';
    } catch (exc) {
      return null;
    }
  }
});


// ********************************************************************************************

$console.extend({
  _appendText: function(object, html){
    html.push($console._escapeHTML($console._objectToString(object)));
  },
  
  _appendNull: function(object, html){
    html.push('<span class="objectBox-null">', $console._escapeHTML($console._objectToString(object)), '</span>');
  },
  
  _appendString: function(object, html){
    html.push('<span class="objectBox-string">&quot;', $console._escapeHTML($console._objectToString(object)),
        '&quot;</span>');
  },
  
  _appendInteger: function(object, html){
    html.push('<span class="objectBox-number">', $console._escapeHTML($console._objectToString(object)), '</span>');
  },
  
  _appendFloat: function(object, html){
    html.push('<span class="objectBox-number">', $console._escapeHTML($console._objectToString(object)), '</span>');
  },
  
  _appendFunction: function(object, html){
    var reName = /function ?(.*?)\(/;
    var m = reName.exec($console._objectToString(object));
    var name = m ? m[1] : 'function';
    html.push('<span class="objectBox-function">', $console._escapeHTML(name), '()</span>');
  },
  
  _appendObject: function(object, html){
    try {
      if (object == undefined)
        $console._appendNull('undefined', html);
      else if (object == null)
        $console._appendNull('null', html);
      else if (typeof object == 'string')
        $console._appendString(object, html);
      else if (typeof object == 'number')
        $console._appendInteger(object, html);
      else if (typeof object == 'function')
        $console._appendFunction(object, html);
      else if (object.nodeType == 1)
        $console._appendSelector(object, html);
      else if (typeof object == 'object')
        $console._appendObjectFormatted(object, html);
      else
        $console._appendText(object, html);
    }
    catch (exc){}
  },
      
  _appendObjectFormatted: function(object, html){
    var text = $console._objectToString(object);
    var reObject = /\[object (.*?)\]/;
  
    var m = reObject.exec(text);
    html.push('<span class="objectBox-object">', m ? m[1] : text, '</span>')
  },
  
  _appendSelector: function(object, html){
    html.push('<span class="objectBox-selector">');
  
    html.push('<span class="selectorTag">', $console._escapeHTML(object.nodeName.toLowerCase()), '</span>');
    if (object.id)
      html.push('<span class="selectorId">#', $console._escapeHTML(object.id), '</span>');
    if (object.className)
      html.push('<span class="selectorClass">.', $console._escapeHTML(object.className), '</span>');
  
    html.push('</span>');
  },
  
  _appendNode: function(node, html){
    if (node.nodeType == 1){
      html.push(
          '<div class="objectBox-element">',
              '&lt;<span class="nodeTag">', node.nodeName.toLowerCase(), '</span>');
  
      for (var i = 0; i < node.attributes.length; ++i){
        var attr = node.attributes[i];
        if (!attr.specified)
            continue;
        
        html.push('&nbsp;<span class="nodeName">', attr.nodeName.toLowerCase(),
            '</span>=&quot;<span class="nodeValue">', $console._escapeHTML(attr.nodeValue),
            '</span>&quot;')
      }
  
      if (node.firstChild){
        html.push('&gt;</div><div class="nodeChildren">');
  
        for (var child = node.firstChild; child; child = child.nextSibling)
            $console._appendNode(child, html);
            
        html.push('</div><div class="objectBox-element">&lt;/<span class="nodeTag">', 
            node.nodeName.toLowerCase(), '&gt;</span></div>');
      
      } else
        html.push('/&gt;</div>');
  
    } else if (node.nodeType == 3) {
      html.push('<div class="nodeText">', $console._escapeHTML(node.nodeValue),
          '</div>');
    }
  }
});

// ********************************************************************************************
$console.extend({
  
  _addEvent: function(object, name, handler){
    if (document.all)
      object.attachEvent('on'+name, handler);
    else
      object.addEventListener(name, handler, false);
  },
  
  _removeEvent: function(object, name, handler){
    if (document.all)
      object.detachEvent('on'+name, handler);
    else
      object.removeEventListener(name, handler, false);
  },
  
  _cancelEvent: function(event){
    if (document.all)
      event.cancelBubble = true;
    else
      event.stopPropagation();        
  },
  
  _onError: function(msg, href, lineNo){
    var html = [];
    
    var lastSlash = href.lastIndexOf('/');
    var fileName = lastSlash == -1 ? href : href.substr(lastSlash+1);
    
    html.push(
        '<span class="errorMessage">', msg, '</span>', 
        '<div class="objectBox-sourceLink">', fileName, ' (line ', lineNo, ')</div>'
      );
    
    $console._logRow(html, 'error');
  },
  
  _onKeyDown: function(event){
    if (event.keyCode == 123)
      $console._toggleConsole();
    else if ((event.keyCode == 108 || event.keyCode == 76) && event.shiftKey
             && (event.metaKey || event.ctrlKey))
      $console._focusCommandLine();
    else
      return;
    
    $console._cancelEvent(event);
  },
  
  _onSplitterMouseDown: function(event){
    if ($console._isSafari || $console._isOpera)
      return;
    
    $console._addEvent(document, 'mousemove', $console._onSplitterMouseMove);
    $console._addEvent(document, 'mouseup', $console._onSplitterMouseUp);
  
    for (var i = 0; i < frames.length; ++i)
    {
      $console._addEvent(frames[i].document, 'mousemove', $console._onSplitterMouseMove);
      $console._addEvent(frames[i].document, 'mouseup', $console._onSplitterMouseUp);
    }
    
    //$event.remove.apply(this, jDev.ui.frame._scrollHandler);
    return false;
  },
  
  _onSplitterMouseMove: function(event){
    var win = document.all
        ? event.srcElement.ownerDocument.parentWindow
        : event.target.ownerDocument.defaultView;
  
    var clientY = event.clientY;
    if (win != win.parent)
        clientY += win.frameElement ? win.frameElement.offsetTop : 0;
    
    var height = $console._consoleFrame.offsetTop + $console._consoleFrame.clientHeight;
    var toolbar = $console._consoleBody.ownerDocument.getElementById('topSplitter');
    var y = Math.max(height - clientY,
                     toolbar.offsetHeight + $console._commandLine.offsetHeight);
    
    $console._consoleFrame.style.height = y + 'px';
    //$console._layout();
    
    setTimeout(jDev.ui._object.draw, 0);    
    
    return false;
  },
  
  _onSplitterMouseUp: function(event){
    $console._removeEvent(document, 'mousemove', $console._onSplitterMouseMove);
    $console._removeEvent(document, 'mouseup', $console._onSplitterMouseUp);
  
    for (var i = 0; i < frames.length; ++i){
      $console._removeEvent(frames[i].document, 'mousemove', $console._onSplitterMouseMove);
      $console._removeEvent(frames[i].document, 'mouseup', $console._onSplitterMouseUp);
    }
    //$event.add.apply(this, jDev.ui.frame._scrollHandler);
  },
  
  _onCommandLineKeyDown: function(event){
    if (event.keyCode == 13)
      $console._evalCommandLine();
    else if (event.keyCode == 27)
      $console._commandLine.value = '';
  }
  
});

function forcingBars(doc) {
  var $ui = jDev.ui;
  
  $console._tsDraw = 0;
  
  $console._consoleFrame = $ui._element;
  $console._consoleBody = $ui._consoleOut;
  $console._commandLine = $ui._commandLine;
  
  var toolbar = doc.getElementById('topSplitter');
  toolbar.onmousedown = $console._onSplitterMouseDown;

  $console._addEvent($console._commandLine, 'keydown', $console._onCommandLineKeyDown);

  //$console._addEvent(doc, $console._isIE || $console._isSafari ? 'keydown' : 'keypress', $console._onKeyDown);
}

/*

window.onerror = $console._onError;
$console._addEvent(
    document, 
    $console._isIE || $console._isSafari ? 'keydown' : 'keypress', 
    $console._onKeyDown
  );
if (document.documentElement.getAttribute('debug') == 'true')
  toggleConsole(true);
  
/**/
  